package cz.data.auth.service;

import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import com.alibaba.nacos.api.exception.NacosException;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.nimbusds.jose.KeySourceException;
import com.nimbusds.jose.jwk.JWKMatcher;
import com.nimbusds.jose.jwk.JWKSelector;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import cz.data.common.core.AuthUser;
import cz.data.common.core.BasicToken;
import cz.data.common.core.DataConstant;
import cz.data.common.core.DataUser;
import cz.data.common.exception.AuthException;
import cz.data.common.redis.config.CacheNames;
import cz.data.common.redis.service.RedisService;
import cz.data.common.utils.SecurityUtil;
import cz.data.domain.system.model.vo.AuthorizeClientVo;
import cz.data.domain.system.rpc.UserServiceFeign;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

@Slf4j
@Component
public class SecurityService {
    private final JWKSelector jwkSelector = new JWKSelector((new JWKMatcher.Builder()).build());
    private final LoadingCache<String, List<ServiceInstance>> serviceCache = Caffeine.newBuilder()
            .expireAfterWrite(Duration.ofMinutes(10))
            .maximumSize(100)
            .build(FunctionCacheLoader.from(this::loadServiceInstance));
    @Autowired
    private NacosServiceDiscovery discovery;
    @Autowired
    private JWKSource<SecurityContext> jwkSource;
    @Autowired
    private RedisService redisService;
    @Autowired
    private UserServiceFeign userServiceFeign;

    public DataUser userinfo() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication instanceof JwtAuthenticationToken) {
            JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;
            String jti = jwtAuthenticationToken.getToken().getClaim("jti");
            String key = DataConstant.Token.JWT_CACHE_PREFIX + jti;
            DataUser dataUser = (DataUser) redisService.get(key);
            if (Objects.isNull(dataUser))
                throw new AuthException("登录已过期");
            Optional.ofNullable(redisService.getExpire(key))
                    .filter(expire -> expire < 600)
                    .ifPresent(expire -> {
                        redisService.expire(key, 60L * 30);
                    });
            return dataUser;
        }
        throw new AuthException("认证失败401");
    }

    public AuthUser authUser(String tokenValue) {
        try {
            BasicToken basicToken = SecurityUtil.converterBasicToken(tokenValue);
            String clientKey = basicToken.getUsername();
            String clientSecret = basicToken.getPassword();
            AuthorizeClientVo authorizeClient = userServiceFeign.loginByAuthorizeClient(clientKey);
            return Optional.ofNullable(authorizeClient)
                    .filter(client -> StringUtils.equals(clientSecret, client.getClientSecret()))
                    .map(client -> {
                        AuthUser user = new AuthUser();
                        user.setId(client.getId());
                        user.setAppKey(client.getClientKey());
                        user.setAppSecret(client.getClientSecret());
                        return user;
                    }).orElse(null);
        } catch (Exception e) {
            return null;
        }
    }

    @CacheEvict(cacheNames = CacheNames.CACHE_10MIN, key = "'auth:userinfo:' + #jti")
    public boolean logout(String jti) {
        if (StringUtils.isNotBlank(jti)) {
            redisService.del(DataConstant.Token.JWT_CACHE_PREFIX + jti);
            redisService.del(DataConstant.Token.JTI_CACHE_PREFIX + jti);
            return true;
        }
        return false;
    }

    public String jwkSource() throws KeySourceException {
        JWKSet jwkSet = new JWKSet(this.jwkSource.get(this.jwkSelector, null));
        return jwkSet.toString();
    }

    public boolean serviceAuth(String token) throws NacosException {
        BasicToken basicToken = SecurityUtil.converterBasicToken(token);
        String serviceName = basicToken.getUsername();
        String clientId = basicToken.getPassword();
        List<ServiceInstance> instances = serviceCache.get(serviceName);
        if (Objects.isNull(instances))
            return false;
        return instances.stream()
                .map(serviceInstance -> serviceInstance.getMetadata().get("client-id"))
                .filter(Objects::nonNull)
                .anyMatch(clientId::equals);
    }

    public void refreshServiceCache(String serviceName, List<ServiceInstance> instances) {
        log.info("刷新nacos实例缓存: {}, 健康节点数: {}", serviceName, instances.size());
        this.serviceCache.put(serviceName, instances);
    }

    private List<ServiceInstance> loadServiceInstance(String serviceName) {
        try {
            log.info("获取远程nacos实例: {}", serviceName);
            return discovery.getInstances(serviceName);
        } catch (NacosException e) {
            return Collections.emptyList();
        }
    }
}
